package com.zhao.util;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.zhao.annotation.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.DynaBean;
import org.apache.commons.beanutils.LazyDynaBean;
import org.apache.commons.beanutils.LazyDynaClass;
import org.apache.commons.beanutils.MutableDynaClass;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * @Author:zhao
 * @Date: 2018/8/1 14:20
 * @Description: Bean解析工具
 */
@Slf4j
@SuppressWarnings("all")
public class BeanParseUtil {

    private BeanParseUtil() {
    }

    private static final Map<Class<?>, Field[]> FIELDS = new LinkedHashMap<>();

    /**
     * 功能描述:Bean对象解析
     *
     * @param targetClass 目标类
     * @param sources     数据源
     * @return
     * @auther zhao
     * @date 2018/8/3 14:55
     */
    public static <T> T parse(Class<T> targetClass, Object... sources) {
        //判断目标类上是否有@DataMapping注解
        boolean isDataMapping = targetClass.isAnnotationPresent(DataMapping.class);
        if (!isDataMapping) {
            log.error("解析对象 - 目标类[{}]:未定义@DataMapping注解", targetClass);
            return null;
        }
        //获取动态Bean
        DynaBean source = getDynaBean(sources);
        if (source == null) {
            log.error("解析对象 - 目标类[{}]:未生成DynaBean", targetClass);
            return null;
        }
        try {
            T target = targetClass.newInstance();
            Field[] fields = getFields(targetClass);
            if (fields == null || fields.length == 0) {
                log.error("解析对象 - 目标类[{}]:未获取到Fields", targetClass);
                return null;
            }
            //反射赋值
            for (Field field : fields) {
                DataValue dataValue = field.getAnnotation(DataValue.class);
                if (dataValue == null) {
                    continue;
                }
                Object value = getValue(targetClass.getAnnotation(DataMapping.class), dataValue, source);
                if (value == null) {
                    log.warn("解析对象 - 目标类[{}] - 目标属性[{}]:未获取到源数据项", targetClass, field.getName());
                    continue;
                }
                try {
                    field.setAccessible(true);
                    if (value.getClass().equals(field.getType())) {
                        field.set(target, value);
                        continue;
                    }

                    log.info("解析对象 - 目标类[{}] - 目标属性[{}]:数据类型不一致", targetClass, field.getName());
                    value = parseValue(value, field.getType());
                    field.set(target, value);
                } catch (Exception e) {
                    log.warn("解析对象 - 目标类[{}] - 目标属性[{}]:赋值失败", targetClass, field.getName());
                }
            }
            return target;
        } catch (Exception e) {
            log.error("解析对象 - 目标类[{}]:{}", targetClass, e);
        }
        return null;
    }

    /**
     * 功能描述:根据Class获取属性集合
     *
     * @param clazz 目标类
     * @return
     * @auther zhao
     * @date 2018/8/3 14:54
     */
    private static Field[] getFields(Class<?> clazz) {
        if (FIELDS.containsKey(clazz)) {
            return FIELDS.get(clazz);
        }
        Field[] fields = clazz.getDeclaredFields();
        FIELDS.put(clazz, fields);
        return fields;
    }

    /**
     * 功能描述: 封装动态Bean
     *
     * @param objects 数据源对象
     * @return
     * @auther zhao
     * @date 2018/8/3 14:53
     */
    private static DynaBean getDynaBean(Object... objects) {
        if (objects.length == 0) {
            return null;
        }
        MutableDynaClass dynaClass = new LazyDynaClass();
        for (Object object : objects) {
            dynaClass.add(object.getClass().getName(), object.getClass());
        }
        DynaBean dynaBean = new LazyDynaBean(dynaClass);
        for (Object object : objects) {
            dynaBean.set(object.getClass().getName(), object);
        }
        return dynaBean;
    }

    /**
     * 获取属性值
     *
     * @param dataValue 注解
     * @param source    动态Bean
     * @return
     */
    private static Object getValue(DataMapping dataMapping, DataValue dataValue, DynaBean source) {
        try {
            //目标值
            Object target = null;
            if (StringUtils.isNotBlank(dataValue.value())) {
                //路径取值
                target = getTarget(dataMapping, dataValue, source);
            }
            DataExpression[] expressions = dataValue.expression();
            if (expressions != null && expressions.length > 0) {
                //表达式取值
                target = getTarget(dataMapping, dataValue.expression(), source);
            }
            if (StringUtils.isNotBlank(dataValue.constant()) && (target == null || (String) target == "")) {
                //默认值
                target = dataValue.constant();
            }
            return target;
        } catch (Exception e) {
            log.error("解析对象 - 取值失败:{}", e);
        }
        return null;
    }

    /**
     * 功能描述: 根据路径获取目标值
     * 1.取得数据源:
     * 1)@DataValue 的source()为Object.class时 使用@DataMapping配置的默认数据源;
     * 2)@DataValue 的source()不为Object.class时 使用source()配置的指定数据源;
     * 3)从DynaBean中根据数据源获取数据对象.
     * 2.路径取值:根据反射从数据对象中获取指定属性的值;
     * 3.JSON处理:@DataValue 的json()不为空时 把属性值转成json 然后根据key获取目标值.
     *
     * @param
     * @return
     * @auther zhao
     * @date 2018/8/3 14:00
     */
    private static Object getTarget(DataMapping dataMapping, DataValue dataValue, DynaBean source) {
        try {
            Class<?> clazz = dataValue.source().equals(Object.class) ? dataMapping.source() : dataValue.source();
            Object target = getTarget(getSource(source, clazz), dataValue.value());
            ParseJson parseJson = dataValue.json();
            if (StringUtils.isNotBlank(parseJson.value())) {
                JSONObject jsonObject = JSON.parseObject(String.valueOf(target));
                target = jsonObject.get(parseJson.value());
            }
            return target;
        } catch (Exception e) {
            log.error("解析对象 - 路径取值失败:{}", e);
        }
        return null;
    }

    /**
     * 功能描述: 根据表达式获取目标值
     *
     * @param
     * @return
     * @auther zhao
     * @date 2018/8/3 14:10
     */
    private static Object getTarget(DataMapping dataMapping, DataExpression[] expressions, DynaBean source) throws Exception {
        if (expressions == null || expressions.length == 0) {
            return null;
        }
        //目标值
        Object target = null;
        for (DataExpression expression : expressions) {
            try {
                Class<?> originalClazz = expression.originalClass().equals(Object.class) ? dataMapping.source() : expression.originalClass();
                Object original = getTarget(getSource(source, originalClazz), expression.original());
                if (original == null) {
                    continue;
                }
                //EG
                if (DataExpressionRuler.EG.getRuler().equals(expression.ruler().getRuler())) {
                    if (StringUtils.isNotBlank(expression.judge()) && StringUtils.equals(expression.judge(), String.valueOf(original))) {
                        Class<?> resultClazz = expression.resultClass().equals(Object.class) ? dataMapping.source() : expression.resultClass();
                        target = getTarget(getSource(source, resultClazz), expression.result());
                    }
                }
                //EACH
                if (DataExpressionRuler.EACH.getRuler().equals(expression.ruler().getRuler())) {
                    List<?> list = (List) original;
                    if (!list.isEmpty()) {
                        for (Object item : list) {
                            Object originalProperty = getTarget(item, expression.originalProperty());
                            if (originalProperty != null && StringUtils.isNotBlank(expression.judge()) && StringUtils.equals(expression.judge(), String.valueOf(originalProperty))) {
                                target = getTarget(item, expression.resultProperty());
                            }
                        }
                    }
                }
            } catch (Exception e) {
                log.warn("解析对象 - 表达式取值失败:{}", e);
            }
        }
        return target;
    }

    /**
     * 获取源数据实体
     *
     * @param source 动态Bean
     * @param clazz  类
     * @return
     */
    private static Object getSource(DynaBean source, Class<?> clazz) {
        if (source == null) {
            return null;
        }
        return source.get(clazz.getName());
    }

    /**
     * 获取目标值
     *
     * @param source 源数据对象
     * @param value  路径
     * @return
     * @throws Exception
     */
    private static Object getTarget(Object source, String value) throws Exception {
        String[] values = value.split("\\.");
        for (String property : values) {
            source = getPropertyValue(source, property);
            if (source == null) {
                break;
            }
        }
        return source;
    }


    /**
     * 根据属性名获取指定实体的数据
     *
     * @param source   源数据实体
     * @param property 属性
     * @return
     */
    private static Object getPropertyValue(Object source, String property) throws Exception {
        Field field = source.getClass().getDeclaredField(property);
        field.setAccessible(true);
        return field.get(source);
    }

    /**
     * 数据强转
     *
     * @param value 源数据实体
     * @param clazz 目标数据类型
     * @return
     */
    private static Object parseValue(Object value, Class<?> clazz) {
        if (BigDecimal.class.equals(clazz)) {
            return new BigDecimal(String.valueOf(value));
        }
        if (LocalDate.class.equals(clazz)) {
            return LocalDate.parse(String.valueOf(value), DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        }
        if (LocalDateTime.class.equals(clazz)) {
            return LocalDateTime.parse(String.valueOf(value), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        }
        if (String.class.equals(clazz)) {
            return String.valueOf(value);
        }
        return value;
    }


}
